package me.zhengjie.modules.poker;

import java.util.*;
import java.util.stream.Collectors;

public class PokerHandEvaluator {

    static class HandResult {
        private final HandType handType;
        private final List<Integer> kickerValues;

        HandResult(HandType handType, List<Integer> kickerValues) {
            this.handType = handType;
            this.kickerValues = kickerValues;
        }

        HandType getHandType() {
            return handType;
        }

        List<Integer> getKickerValues() {
            return kickerValues;
        }
    }

    public static void main(String[] args) {
        List<Card> hand1 = Arrays.asList(
                Card.fromStrings("8", "梅花"),
                Card.fromStrings("9", "方片"),
                Card.fromStrings("7", "黑桃"),
                Card.fromStrings("2", "红心"),
                Card.fromStrings("4", "红心")
        );

        List<Card> hand2 = Arrays.asList(
                Card.fromStrings("8", "梅花"),
                Card.fromStrings("9", "方片"),
                Card.fromStrings("7", "黑桃"),
                Card.fromStrings("6", "红心"),
                Card.fromStrings("3", "红心")
        );

        printHandResult(hand1);
        printHandResult(hand2);

        if (compareHands(hand1, hand2) > 0) {
            System.out.println("Hand 1 赢了！");
        } else if (compareHands(hand1, hand2) < 0) {
            System.out.println("Hand 2 赢了！");
        } else {
            System.out.println("平局！");
        }
    }

    private static void printHandResult(List<Card> hand) {
        HandResult result = evaluateHand(hand);
        System.out.print("手牌: ");
        for (Card card : hand) {
            System.out.print(card + " ");
        }
        System.out.println("\n牌型: " + result.getHandType().getChineseName());
        System.out.println("大小: " + result.getHandType().toString());
        System.out.println();
    }

    private static HandResult evaluateHand(List<Card> cards) {
        Collections.sort(cards);
        boolean isFlush = isFlush(cards);
        boolean isStraight = isStraight(cards);

        Map<Integer, Long> rankCounts = cards.stream()
                .collect(Collectors.groupingBy(card -> card.getRank().getValue(), Collectors.counting()));

        long fourOfAKindCount = rankCounts.values().stream().filter(count -> count == 4).count();
        long threeOfAKindCount = rankCounts.values().stream().filter(count -> count == 3).count();
        long pairCount = rankCounts.values().stream().filter(count -> count == 2).count();

        if (isFlush && isStraight) {
            if (cards.get(4).getRank() == Rank.ACE) {
                return new HandResult(HandType.ROYAL_FLUSH, Collections.emptyList());
            } else {
                return new HandResult(HandType.STRAIGHT_FLUSH, Collections.singletonList(cards.get(4).getRank().getValue()));
            }
        } else if (fourOfAKindCount == 1) {
            return new HandResult(HandType.FOUR_OF_A_KIND, extractRanksWithCount(rankCounts, 4));
        } else if (threeOfAKindCount == 1 && pairCount == 1) {
            return new HandResult(HandType.FULL_HOUSE, extractRanksWithCount(rankCounts, 3));
        } else if (isFlush) {
            return new HandResult(HandType.FLUSH, cards.stream().map(card -> card.getRank().getValue()).toList());
        } else if (isStraight) {
            return new HandResult(HandType.STRAIGHT, Collections.singletonList(cards.get(4).getRank().getValue()));
        } else if (threeOfAKindCount == 1) {
            return new HandResult(HandType.THREE_OF_A_KIND, extractRanksWithCount(rankCounts, 3));
        } else if (pairCount == 2) {
            return new HandResult(HandType.TWO_PAIR, extractRanksWithCount(rankCounts, 2));
        } else if (pairCount == 1) {
            return new HandResult(HandType.ONE_PAIR, extractRanksWithCount(rankCounts, 2));
        } else {
            return new HandResult(HandType.HIGH_CARD, cards.stream().map(card -> card.getRank().getValue()).toList());
        }
    }

    private static boolean isFlush(List<Card> cards) {
        Suit suit = cards.get(0).getSuit();
        for (Card card : cards) {
            if (card.getSuit() != suit) {
                return false;
            }
        }
        return true;
    }

    private static boolean isStraight(List<Card> cards) {
        for (int i = 1; i < cards.size(); i++) {
            if (cards.get(i).getRank().getValue() != cards.get(i - 1).getRank().getValue() + 1) {
                return false;
            }
        }
        return true;
    }

    private static List<Integer> extractRanksWithCount(Map<Integer, Long> rankCounts, long count) {
        return rankCounts.entrySet().stream()
                .filter(entry -> entry.getValue() == count)
                .sorted((e1, e2) -> Integer.compare(e2.getKey(), e1.getKey()))
                .map(Map.Entry::getKey)
                .limit(1)
                .toList();
    }

    private static int compareHands(List<Card> hand1, List<Card> hand2) {
        HandResult result1 = evaluateHand(hand1);
        HandResult result2 = evaluateHand(hand2);

        int typeComparison = Integer.compare(result1.getHandType().ordinal(), result2.getHandType().ordinal());
        if (typeComparison != 0) {
            return typeComparison;
        }

        List<Integer> kickers1 = result1.getKickerValues();
        List<Integer> kickers2 = result2.getKickerValues();

        // Compare kickers in descending order
        for (int i = 0; i < Math.min(kickers1.size(), kickers2.size()); i++) {
            int comparison = Integer.compare(kickers1.get(i), kickers2.get(i));
            if (comparison != 0) {
                return comparison;
            }
        }

        // If all kickers are equal, compare remaining high cards
        Collections.sort(hand1, Comparator.comparingInt((Card c) -> c.getRank().getValue()).reversed());
        Collections.sort(hand2, Comparator.comparingInt((Card c) -> c.getRank().getValue()).reversed());

        for (int i = 0; i < 5; i++) {
            int comparison = Integer.compare(hand1.get(i).getRank().getValue(), hand2.get(i).getRank().getValue());
            if (comparison != 0) {
                return comparison;
            }
        }

        return 0;
    }
}